package com.swak.telnet.transport;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

import com.swak.exception.NoSupportException;
import com.swak.exception.RemotingException;
import com.swak.reactivex.transport.Channel;
import com.swak.reactivex.transport.Codec;
import com.swak.reactivex.transport.Message;
import com.swak.reactivex.transport.exception.CodecException;
import com.swak.reactivex.transport.exception.ExchangeException.ExchangeMethod;
import com.swak.registry.URL;
import com.swak.telnet.cmd.Command;
import com.swak.utils.CollectionUtils;
import com.swak.utils.StringUtils;

import io.netty.buffer.ByteBuf;

/**
 * 协议编码解码器: 只输出 data 数据部分
 * 
 * @author lifeng
 */
public class StringCodec implements Codec {
	private static final Charset DEFAULT_ENCODING = Charset.forName("GB2312");
	private static final String HISTORY_LIST_KEY = "telnet.history.list";
	private static final String HISTORY_INDEX_KEY = "telnet.history.index";
	private static final byte[] UP = new byte[] { 27, 91, 65 };

	private static final byte[] DOWN = new byte[] { 27, 91, 66 };

	private static final List<?> ENTER = Arrays.asList(new byte[] { '\r', '\n' } /* Windows Enter */,
			new byte[] { '\n' } /* Linux Enter */);

	private static final List<?> EXIT = Arrays.asList(new byte[] { 3 } /* Windows Ctrl+C */,
			new byte[] { -1, -12, -1, -3, 6 } /* Linux Ctrl+C */, new byte[] { -1, -19, -1, -3, 6 } /* Linux Pause */);

	private static boolean isEquals(byte[] message, byte[] command) throws IOException {
		return message.length == command.length && endsWith(message, command);
	}

	private static boolean endsWith(byte[] message, byte[] command) throws IOException {
		if (message.length < command.length) {
			return false;
		}
		int offset = message.length - command.length;
		for (int i = command.length - 1; i >= 0; i--) {
			if (message[offset + i] != command[i]) {
				return false;
			}
		}
		return true;
	}

	protected final URL url;

	public StringCodec(URL url) {
		this.url = url;
	}

	@Override
	public void encode(Channel channel, ByteBuf buffer, Object message) throws CodecException {
		if (message instanceof String) {
			message = message + "\r\n";
			byte[] msgData = ((String) message).getBytes(DEFAULT_ENCODING);
			buffer.writeBytes(msgData);
		} else if (message instanceof Message) {
			String command = encode((Message) message);
			command = command + "\r\n";
			byte[] msgData = ((String) command).getBytes(DEFAULT_ENCODING);
			buffer.writeBytes(msgData);
		} else {
			throw new NoSupportException("不支持此类型的消息:" + message.getClass().getSimpleName());
		}
	}

	/**
	 * 格式化输出
	 * 
	 * @param message
	 * @return
	 */
	protected String encode(Message message) {
		return message.getData() != null ? message.getData().toString() : StringUtils.EMPTY;
	}

	@Override
	public Object decode(Channel channel, ByteBuf buffer) throws CodecException {
		try {
			int readable = buffer.readableBytes();
			byte[] message = new byte[readable];
			buffer.readBytes(message);
			Object result = decode(channel, buffer, readable, message);

			// 没有解析到数据则不用处理
			if (result == null || (result instanceof String && StringUtils.isBlank((String) result))
					|| result == DecodeResult.NEED_MORE_INPUT) {
				return result;
			}

			// 解析成命令
			return Command.parse(result.toString()).toMessage();
		} catch (Throwable t) {
			throw new CodecException(ExchangeMethod.Read, t.getMessage(), t);
		}
	}

	@SuppressWarnings("unchecked")
	protected Object decode(Channel channel, ByteBuf buffer, int readable, byte[] message) throws IOException {
		if (message == null || message.length == 0) {
			return DecodeResult.NEED_MORE_INPUT;
		}

		if (message[message.length - 1] == '\b') { // Windows backspace echo
			try {
				boolean doublechar = message.length >= 3 && message[message.length - 3] < 0; // double byte char
				channel.send(
						new String(doublechar ? new byte[] { 32, 32, 8, 8 } : new byte[] { 32, 8 }, DEFAULT_ENCODING));
			} catch (RemotingException e) {
				throw new IOException(e);
			}
			return DecodeResult.NEED_MORE_INPUT;
		}

		for (Object command : EXIT) {
			if (isEquals(message, (byte[]) command)) {
				channel.close();
				return null;
			}
		}

		boolean up = endsWith(message, UP);
		boolean down = endsWith(message, DOWN);
		if (up || down) {
			LinkedList<String> history = (LinkedList<String>) channel.getAttribute(HISTORY_LIST_KEY);
			if (CollectionUtils.isEmpty(history)) {
				return DecodeResult.NEED_MORE_INPUT;
			}
			Integer index = (Integer) channel.getAttribute(HISTORY_INDEX_KEY);
			if (index == null) {
				index = history.size() - 1;
			} else {
				if (up) {
					index = index - 1;
					if (index < 0) {
						index = history.size() - 1;
					}
				} else {
					index = index + 1;
					if (index > history.size() - 1) {
						index = 0;
					}
				}
			}
			channel.setAttribute(HISTORY_INDEX_KEY, index);
			return history.get(index);
		}
		for (Object command : EXIT) {
			if (isEquals(message, (byte[]) command)) {
				channel.close();
				return null;
			}
		}
		byte[] enter = null;
		for (Object command : ENTER) {
			if (endsWith(message, (byte[]) command)) {
				enter = (byte[]) command;
				break;
			}
		}
		if (enter == null) {
			return DecodeResult.NEED_MORE_INPUT;
		}
		String result = new String(message, DEFAULT_ENCODING);
		result = StringUtils.trimToEmpty(result);
		LinkedList<String> history = (LinkedList<String>) channel.getAttribute(HISTORY_LIST_KEY);
		if (result.trim().length() > 0) {
			if (history == null) {
				history = new LinkedList<String>();
				channel.setAttribute(HISTORY_LIST_KEY, history);
			}
			if (history.isEmpty()) {
				history.addLast(result);
			} else if (!result.equals(history.getLast())) {
				history.remove(result);
				history.addLast(result);
				if (history.size() > 10) {
					history.removeFirst();
				}
			}
		}
		return result;
	}
}